<template>
  <div class="cl-upload__wrap">
    <div class="cl-upload"
         :class="[
				`cl-upload--${listType}`,
				{
					'is-multiple': multiple,
					'is-drag': drag
				}
			]">
      <el-input class="cl-upload__hidden"
                type="hidden" />

      <el-upload :action="action"
                 :accept="_accept"
                 :multiple="multiple"
                 :limit="limit"
                 :data="data"
                 :name="_name"
                 :drag="drag"
                 :list-type="listType"
                 :file-list="fileList"
                 :show-file-list="_showFileList"
                 :auto-upload="autoUpload"
                 :disabled="disabled"
                 v-loading="_loading"
                 :headers="{
					Authorization: token,
					...headers
				}"
                 :http-request="action ? undefined : httpRequest"
                 :on-remove="_onRemove"
                 :on-preview="_onPreview"
                 :on-progress="onProgress"
                 :on-change="onChange"
                 :on-exceed="onExceed"
                 :before-upload="_beforeUpload"
                 :before-remove="beforeRemove"
                 :style="_style">
        <slot>
          <!-- 多图上传 -->
          <template v-if="listType == 'picture-card'">
            <i :class="['cl-upload__icon', _icon]"></i>
            <span v-if="_text"
                  class="cl-upload__text">{{ _text }}</span>
          </template>

          <!-- 文件上传 -->
          <template v-else-if="listType == 'text'">
            <el-button size="mini"
                       type="primary"
                       :loading="loading">点击上传</el-button>
          </template>

          <!-- 默认上传 -->
          <template v-else>
            <template v-if="_file">
              <div class="cl-upload__cover">
                <!-- 图片 -->
                <img v-if="_file.type == 'image'"
                     :src="_file.url" />

                <!-- 文件名 -->
                <span v-else>{{ _file.name }}</span>
              </div>

              <!-- 功能按钮 -->
              <div class="cl-upload__actions">
                <span v-if="_file.type == 'image'"
                      class="cl-upload__actions-preview"
                      @click.stop="_onPreview(_file)">
                  <i class="el-icon-zoom-in"></i>
                </span>

                <span class="cl-upload__actions-delete"
                      @click.stop="_onRemove(_file)">
                  <i class="el-icon-delete"></i>
                </span>
              </div>
            </template>

            <!-- 空态 -->
            <template v-else>
              <i :class="['cl-upload__icon', _icon]"></i>
              <span v-if="_text"
                    class="cl-upload__text">{{ _text }}</span>
            </template>
          </template>
        </slot>
      </el-upload>
    </div>

    <cl-dialog v-model="preview.visible"
               title="图片预览"
               :props="{
				width: previewWidth
			}">
      <img style="width: 100%"
           :src="preview.url"
           alt="" />
    </cl-dialog>
  </div>
</template>

<script>
import { mapGetters } from "vuex";
import { clone, last, isArray, isNumber, isBoolean, basename } from "/@/cool/utils";
import { v4 as uuidv4 } from "uuid";

export default {
  name: "cl-upload",

  props: {
    modelValue: [Array, String],
    // 尺寸
    size: [Array, String, Number],
    // 显示图标
    icon: String,
    // 显示文案
    text: String,
    // 是否以 uuid 重命名
    rename: {
      type: Boolean,
      default: undefined
    },
    // 最大允许上传文件大小(MB)
    limitSize: Number,
    // 预览图片的宽度
    previewWidth: {
      type: String,
      default: "500px"
    },
    // 上传的地址
    action: {
      type: String,
      default: ""
    },
    // 设置上传的请求头部
    headers: Object,
    // 是否多选
    multiple: Boolean,
    // 上传时附带的额外参数
    data: Object,
    // 上传的文件字段名
    name: String,
    // 支持发送 cookie 凭证信息
    withCredentials: Boolean,
    // 是否显示已上传文件列表
    showFileList: {
      type: Boolean,
      default: undefined
    },
    // 是否拖拽
    drag: Boolean,
    // 上传的文件类型
    accept: String,
    // 文件列表的类型
    listType: {
      type: String,
      default: "default"
    },
    // 是否自带上传
    autoUpload: {
      type: Boolean,
      default: true
    },
    // 是否禁用
    disabled: Boolean,
    // 最大允许上传个数
    limit: Number,
    // 文件列表移除文件时的钩子
    onRemove: Function,
    // 点击文件列表中已上传的文件时的钩子
    onPreview: Function,
    // 文件上传成功时的钩子
    onSuccess: Function,
    // 文件上传失败时的钩子
    onError: Function,
    // 文件上传时的钩子
    onProgress: Function,
    // 文件状态改变时的钩子
    onChange: Function,
    // 文件超出个数限制时的钩子
    onExceed: Function,
    // 上传文件之前的钩子
    beforeUpload: Function,
    // 删除文件之前的钩子
    beforeRemove: Function,
    // 是否拼接
    urlJoin: {
      type: Boolean,
      default: true
    }
  },

  emits: ["update:modelValue", "change"],

  data () {
    return {
      fileList: [],
      urls: [],
      loading: false,
      preview: {
        visible: false,
        url: ""
      }
    };
  },

  computed: {
    ...mapGetters(["token", "modules"]),

    conf () {
      return this.modules.upload.options;
    },

    _size () {
      return this.size || this.conf.size || "128px";
    },

    _icon () {
      return this.icon || this.conf.icon || "el-icon-upload";
    },

    _text () {
      return this.text === undefined ? this.conf.text : this.text;
    },

    _accept () {
      const d = this.accept || this.conf.accept;

      switch (this.listType) {
        case "picture-card":
          return d || "image/*";
        default:
          return d;
      }
    },

    _name () {
      return this.name || this.conf.name || "file";
    },

    _limitSize () {
      return this.limitSize || this.conf.limitSize || 10;
    },

    _rename () {
      return isBoolean(this.rename) ? this.rename : this.conf.rename;
    },

    _showFileList () {
      let f = null;

      switch (this.listType) {
        case "picture-card":
        case "text":
          f = true;
          break;
        default:
          f = false;
          break;
      }

      return this.showFileList === undefined ? f : this.showFileList;
    },

    _loading () {
      return this.listType == "default" ? this.loading : false;
    },

    _urls () {
      return this.urls.filter((e) => Boolean(e.url));
    },

    _file () {
      const d = this._urls[0];

      if (d) {
        const suf = last(d.url.split(".")).toLowerCase();

        if (["bmp", "jpg", "jpeg", "png", "tif", "gif", "svg", "webp"].includes(suf)) {
          d.type = "image";
        }

        return d;
      } else {
        return null;
      }
    },

    _style () {
      let arr = [];

      if (isArray(this._size)) {
        arr = this._size;
      } else {
        arr = [this._size, this._size];
      }

      const [height, width] = arr.map((e) => (isNumber(e) ? `${e}px` : e));

      if (this.listType == "default" && !this.drag) {
        return {
          height,
          width
        };
      } else {
        return {};
      }
    }
  },

  watch: {
    modelValue: {
      immediate: true,
      handler: "parseValue"
    }
  },

  methods: {
    // 解析参数
    parseValue (value) {
      let list = [];

      if (this.multiple) {
        if (value instanceof Array) {
          list = value;
        } else {
          list = (value || "").split(",");
        }
      } else {
        if (value) {
          list = [value];
        }
      }

      // 比较数据，避免重复动画
      if (
        !this.urls.some((e) => {
          return list.includes(e.url);
        })
      ) {
        this.fileList = list.filter(Boolean).map((url) => {
          return {
            url,
            name: basename(url),
            uid: url
          };
        });

        // 设置 URLS
        this.urls = clone(this.fileList);
      }
    },

    // 更新值
    update () {
      let urls = this.urls.filter((e) => Boolean(e.url)).map((e) => e.url);

      // 是否拼接
      if (this.urlJoin) {
        urls = urls.join(",");
      }

      this.$emit("update:modelValue", urls);
      this.$emit("change", urls);
    },

    // 追加文件
    append (data) {
      if (this.multiple) {
        this.urls.push(data);
      } else {
        this.urls = [data];
      }

      this.update();
    },

    // 关闭上传加载中
    done () {
      this.loading = false;
    },

    // 删除文件
    _onRemove (file) {
      this.urls.splice(
        this.urls.findIndex((e) => e.uid === file.uid),
        1
      );
      this.update();

      // 删除文件之前的钩子
      if (this.onRemove) {
        this.onRemove(file, this.urls);
      }
    },

    // 预览图片
    _onPreview (file) {
      let url = "";

      if (file.raw) {
        if (file.raw.type.indexOf("image/") == 0) {
          const item = this.urls.find((e) => e.uid == file.uid);

          if (item) {
            url = item.url;
          }
        }
      } else {
        if (file.type == "image") {
          url = file.url;
        }
      }

      if (url) {
        this.preview.visible = true;
        this.preview.url = url;
      }
    },

    // 上传前
    _beforeUpload (file) {
      this.loading = true;

      if (this._limitSize) {
        if (file.size / 1024 / 1024 >= this._limitSize) {
          this.$message.error(`上传文件大小不能超过 ${this._limitSize}MB!`);
          this.done();
          return false;
        }
      }

      if (this.beforeUpload) {
        return this.beforeUpload(file, {
          done: this.done
        });
      }

      return true;
    },

    // 上传成功
    _onSuccess (res, file) {
      this.loading = false;

      this.append({
        url: res.data,
        name: file.raw.name,
        uid: file.raw.uid
      });

      // 文件上传成功时的钩子
      if (this.onSuccess) {
        this.onSuccess(res, file.raw, this.urls);
      }
    },

    // 重设上传请求
    async httpRequest (req) {
      const mode = await this.uploadMode();

      // 多种上传请求
      const upload = (file) => {
        let fileName = file.name;
        // 是否以 uuid 重新命名
        if (this._rename) {
          fileName = uuidv4() + "." + last((file.name || "").split("."));
        }
        return new Promise((resolve, reject) => {
          const next = (res) => {
            let headers = {};
            let updateData;
            let uploadUrl;
            let uploadMethod = "POST";
            //如果返回的数据中已经包含了uploadUrl，说明是预签名上传，就直接传文件就好了
            if (res.uploadUrl) {
              uploadUrl = res.uploadUrl;
              if (res.uploadData) {
                const data = new FormData();
                for (const i in res.uploadData) {
                  if (i != "fileKey") {
                    data.append(i, res.uploadData[i]);
                  }
                }
                data.append(res.uploadData.fileKey || "file", file);
                headers = {
                  "Content-Type": "multipart/form-data"
                };
                updateData = data;

              } else {
                updateData = file;
              }
              uploadMethod = res.method || uploadMethod;
            } else {
              uploadUrl = res.host;
              const data = new FormData();
              for (const i in res) {
                if (i != "host") {
                  data.append(i, res[i]);
                }
              }

              data.append("key", `app/${fileName}`);
              data.append("file", file);
              headers = {
                "Content-Type": "multipart/form-data"
              };
              updateData = data;
            }

            // 上传
            this.service.base.common
              .request({
                url: uploadUrl,
                method: uploadMethod,
                headers,
                data: updateData,
                onUploadProgress: (e) => {
                  if (this.onProgress) {
                    e.percent = parseInt((e.loaded / e.total) * 100);
                    this.onProgress(e, req.file);
                  }
                }
              })
              .then((url) => {
                if (mode === "local") {
                  resolve(url);
                } else {
                  //如果前边的upload接口返回里包含了publicUrl,就优先使用publicUrl
                  resolve(res.publicUrl || `${res.host}/app/${fileName}`);
                }
              })
              .catch((err) => {
                reject(err);
              });
          };

          if (mode == "local") {
            next({
              host: "/upload"
            });
          } else {
            this.service.base.common
              .upload({ fileName: fileName, folder: "/upload" })
              .then((res) => {
                next(res);
              })
              .catch(reject);
          }
        });
      };

      this.loading = true;

      await upload(req.file)
        .then((url) => {
          this._onSuccess({ data: url }, { raw: req.file });
        })
        .catch((err) => {
          console.error("upload error", err);
          this.$message.error(err);

          // 	文件上传失败时的钩子
          if (this.onError) {
            this.onError(err, req.file);
          }
        });

      this.loading = false;
    },



    // 上传模式
    uploadMode () {
      return this.service.base.common.uploadMode().then((res) => res.mode);
    }
  }
};
</script>

<style lang="scss">
.cl-upload {
	display: flex;
	flex-wrap: wrap;

	&__hidden {
		height: 0;
		width: 0;
	}

	&.is-multiple {
		.cl-upload__wrap {
			margin-right: 10px;
		}
	}

	&--default {
		&:not(.is-drag) {
			.el-upload {
				display: flex;
				align-items: center;
				justify-content: center;
				border: 1px dashed #d9d9d9;
				border-radius: 6px;
				cursor: pointer;
				position: relative;
				overflow: hidden;
				height: 100%;

				i {
					font-size: 28px;
					color: #8c939d;
				}

				.cl-upload__cover {
					position: relative;

					img {
						display: block;
						height: 100%;
						width: 100%;
					}
				}

				.cl-upload__actions {
					position: absolute;
					width: 100%;
					height: 100%;
					left: 0;
					top: 0;
					cursor: default;
					text-align: center;
					color: #fff;
					opacity: 0;
					font-size: 20px;
					background-color: rgba(0, 0, 0, 0.5);
					-webkit-transition: opacity 0.3s;
					transition: opacity 0.3s;

					&::after {
						display: inline-block;
						content: "";
						height: 100%;
						vertical-align: middle;
					}

					span {
						display: none;
						cursor: pointer;
					}

					span + span {
						margin-left: 15px;
					}

					i {
						color: #fff;
						font-size: 20px;
					}
				}

				&:hover {
					border-color: $color-primary;

					.cl-upload__actions {
						opacity: 1;

						span {
							display: inline-block;
						}
					}
				}
			}
		}
	}

	&--picture-card {
		.el-upload {
			background-color: #fff;

			.cl-upload__icon {
				position: relative;
				top: 4px;
			}
		}
	}

	&__icon + span {
		margin-left: 5px;
	}

	&__text {
		font-size: 14px;
	}
}
</style>
